﻿using System;
using System.Windows;

namespace HandyControl.Interactivity;

internal sealed class NameResolver
{
    private string _name;
    private FrameworkElement _nameScopeReferenceElement;

    private FrameworkElement ActualNameScopeReferenceElement
    {
        get
        {
            if (NameScopeReferenceElement != null && Interaction.IsElementLoaded(NameScopeReferenceElement))
                return GetActualNameScopeReference(NameScopeReferenceElement);
            return null;
        }
    }

    private bool HasAttempedResolve { get; set; }

    public string Name
    {
        get =>
            _name;
        set
        {
            var oldObject = Object;
            _name = value;
            UpdateObjectFromName(oldObject);
        }
    }

    public FrameworkElement NameScopeReferenceElement
    {
        get =>
            _nameScopeReferenceElement;
        set
        {
            var nameScopeReferenceElement = NameScopeReferenceElement;
            _nameScopeReferenceElement = value;
            OnNameScopeReferenceElementChanged(nameScopeReferenceElement);
        }
    }

    public DependencyObject Object
    {
        get
        {
            if (string.IsNullOrEmpty(Name) && HasAttempedResolve)
                return NameScopeReferenceElement;
            return ResolvedObject;
        }
    }

    private bool PendingReferenceElementLoad { get; set; }

    private DependencyObject ResolvedObject { get; set; }

    public event EventHandler<NameResolvedEventArgs> ResolvedElementChanged;

    private FrameworkElement GetActualNameScopeReference(FrameworkElement initialReferenceElement)
    {
        var element = initialReferenceElement;
        if (!IsNameScope(initialReferenceElement))
            return element;
        return initialReferenceElement.Parent as FrameworkElement ?? element;
    }

    private bool IsNameScope(FrameworkElement frameworkElement)
    {
        return frameworkElement.Parent is FrameworkElement parent && parent.FindName(Name) != null;
    }

    private void OnNameScopeReferenceElementChanged(FrameworkElement oldNameScopeReference)
    {
        if (PendingReferenceElementLoad)
        {
            oldNameScopeReference.Loaded -= OnNameScopeReferenceLoaded;
            PendingReferenceElementLoad = false;
        }
        HasAttempedResolve = false;
        UpdateObjectFromName(Object);
    }

    private void OnNameScopeReferenceLoaded(object sender, RoutedEventArgs e)
    {
        PendingReferenceElementLoad = false;
        NameScopeReferenceElement.Loaded -= OnNameScopeReferenceLoaded;
        UpdateObjectFromName(Object);
    }

    private void OnObjectChanged(DependencyObject oldTarget, DependencyObject newTarget)
    {
        ResolvedElementChanged?.Invoke(this, new NameResolvedEventArgs(oldTarget, newTarget));
    }

    private void UpdateObjectFromName(DependencyObject oldObject)
    {
        DependencyObject obj2 = null;
        ResolvedObject = null;
        if (NameScopeReferenceElement != null)
        {
            if (!Interaction.IsElementLoaded(NameScopeReferenceElement))
            {
                NameScopeReferenceElement.Loaded += OnNameScopeReferenceLoaded;
                PendingReferenceElementLoad = true;
                return;
            }
            if (!string.IsNullOrEmpty(Name))
            {
                var actualNameScopeReferenceElement = ActualNameScopeReferenceElement;
                if (actualNameScopeReferenceElement != null)
                    obj2 = actualNameScopeReferenceElement.FindName(Name) as DependencyObject;
            }
        }
        HasAttempedResolve = true;
        ResolvedObject = obj2;
        if (!Equals(oldObject, Object))
            OnObjectChanged(oldObject, Object);
    }
}
